package com.thinkaurelius.titan.diskstorage.infinispan;
import java.io.IOException;
import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.NotSupportedException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;
import org.infinispan.manager.EmbeddedCacheManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Preconditions;
import com.thinkaurelius.titan.diskstorage.StaticBuffer;
import com.thinkaurelius.titan.diskstorage.StorageException;
import com.thinkaurelius.titan.diskstorage.common.NoOpStoreTransaction;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTransaction;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.StoreTxConfig;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.keyvalue.KeySelector;
import com.thinkaurelius.titan.diskstorage.keycolumnvalue.keyvalue.KeyValueEntry;
import com.thinkaurelius.titan.diskstorage.util.RecordIterator;
public class InfinispanCacheTransactionalStore extends InfinispanCacheStore {
private static final Logger log = LoggerFactory.getLogger(InfinispanCacheTransactionalStore.class);
private final NoOpStoreTransaction noopTx = new NoOpStoreTransaction(new StoreTxConfig()); // TODO pass through actual tx config? i think this just affects metrics
public InfinispanCacheTransactionalStore(String fullName, String shortName, EmbeddedCacheManager manager) {
super(fullName, shortName, manager);
}
@Override
public void delete(StaticBuffer key, StoreTransaction txh)
throws StorageException {
InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
try {
ict.resume();
super.delete(key, noopTx);
} finally {
ict.suspend();
}
}
@Override
public StaticBuffer get(StaticBuffer key, StoreTransaction txh)
throws StorageException {
InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
try {
ict.resume();
return super.get(key, noopTx);
} finally {
ict.suspend();
}
}
@Override
public boolean containsKey(StaticBuffer key, StoreTransaction txh)
throws StorageException {
InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
try {
ict.resume();
return super.containsKey(key, noopTx);
} finally {
ict.suspend();
}
}
@Override
public void acquireLock(StaticBuffer key, StaticBuffer expectedValue,
StoreTransaction txh) throws StorageException {
// Do nothing
}
@Override
public StaticBuffer[] getLocalKeyPartition() throws StorageException {
return null; // TODO
}
@Override
public String getName() {
return super.getName();
}
@Override
public void close() throws StorageException {
super.close();
}
@Override
public void replace(StaticBuffer key, StaticBuffer newValue,
StaticBuffer oldValue, StoreTransaction txh)
throws StorageException {
InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
try {
ict.resume();
super.replace(key, newValue, oldValue, noopTx);
} finally {
ict.suspend();
}
}
@Override
public RecordIterator<KeyValueEntry> getKeys(KeySelector selector,
StoreTransaction txh) throws StorageException {
InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
try {
ict.resume();
/*
* Iterator returned by super.getKeys(...) does lazy cache.get(...)
* calls when next() is invoked. We must wrap it with our
* transaction context, else these iterators calls will essentially
* ignore the transactional context. The behavior observed in this
* case is correct keys but nonsensical values, and this in turn is
* likely to break the "null != value" assertions in KeyValueEntry.
*/
return new ISTxKeyIter(super.getKeys(selector, noopTx), ict);
} finally {
ict.suspend();
}
}
@Override
public void clearStore() {
TransactionManager tm = cache.getAdvancedCache().getTransactionManager();
try {
tm.begin();
super.clearStore();
tm.commit();
} catch (NotSupportedException e) {
throw new RuntimeException(e);
} catch (SystemException e) {
throw new RuntimeException(e);
} catch (SecurityException e) {
throw new RuntimeException(e);
} catch (IllegalStateException e) {
throw new RuntimeException(e);
} catch (RollbackException e) {
throw new RuntimeException(e);
} catch (HeuristicMixedException e) {
throw new RuntimeException(e);
} catch (HeuristicRollbackException e) {
throw new RuntimeException(e);
}
}
// private <T> T runInTx(Callable<T> f) {
// InfinispanCacheTransaction ict = (InfinispanCacheTransaction)txh;
// boolean sameOrNullCache = ict.init(cache);
// if (!sameOrNullCache) {
// log.error("Infinispan transaction spans multiple caches ({} and {})", cache, ict.getCache());
// }
// try {
// ict.resume();
// return f.call();
// } finally {
// ict.suspend();
// }
// }
private static class ISTxKeyIter implements RecordIterator<KeyValueEntry> {
private final RecordIterator<KeyValueEntry> underlying;
private final InfinispanCacheTransaction cacheTx;
public ISTxKeyIter(RecordIterator<KeyValueEntry> underlying, InfinispanCacheTransaction cacheTx) {
this.underlying = underlying;
this.cacheTx = cacheTx;
}
@Override
public boolean hasNext() {
/*
* Underlying impl iterates over a collection eagerly retrieved
* during getKeys(...) invocation. No need to wrap it with a
* transactional context.
*/
return underlying.hasNext();
}
@Override
public KeyValueEntry next() {
// Underlying does an ISPN cache.get(...), must wrap with tx
try {
cacheTx.resume();
return underlying.next();
} catch (StorageException e) {
throw new RuntimeException(e);
} finally {
try {
cacheTx.suspend();
} catch (StorageException e) {
log.warn("Failed to suspend Infinispan transaction {}", cacheTx, e);
}
}
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
@Override
public void close() throws IOException {
// Do nothing
}
}
}